﻿using Abp.Auditing;
using Abp.Events.Bus;
using AutoMapper;
using GN.Pay.Core.Entities;
using GN.Pay.Core.Exceptions;
using GN.Pay.Core.Repositories;
using GN.Pay.Core.Tasks.Queue;
using GN.Pay.Core.Services;
using GN.Pay.Core.Values;
using GN.Pay.Core.Values.TradeRefund;
using GN.Pay.TradeChannel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Abp.AutoMapper;
using System.Threading.Tasks;
using static GN.Pay.Core.Consts.ServiceConstant;

namespace GN.Pay.Core.Services.Impl
{
    /// <summary>
    /// 交易退款申请领域服务
    /// </summary>
    class TradeRefundApplyService : ATradeChannelApplyService, ITradeRefundApplyService
    {
        private readonly ITradeRefundApplyRepository repository;
        private readonly ITradeAccountBookRepository bookRepository;
        private readonly ITradeRefundBookRepository refundBookRepositor;
        private readonly ITradeCacheService tradeCacheService;
        private readonly IQueueManager queueManager;

        public IClientInfoProvider ClientInfo { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repository"></param>
        /// <param name="bookRepository"></param>
        /// <param name="refundBookRepository"></param>
        /// <param name="tradeNotifyDomainService"></param>
        /// <param name=""></param>
        public TradeRefundApplyService(ITradeRefundApplyRepository repository,
            ITradeAccountBookRepository bookRepository,
            ITradeRefundBookRepository refundBookRepository,
            ITradeCacheService tradeCacheService,
            IQueueManager queueManager)
        {
            this.repository = repository;
            this.bookRepository = bookRepository;
            this.refundBookRepositor = refundBookRepository;
            this.tradeCacheService = tradeCacheService;
            this.queueManager = queueManager;
            ClientInfo = Abp.Auditing.NullClientInfoProvider.Instance;
        }

        #region 申请相关函数

        /// <summary>
        /// 获取申请转结果
        /// </summary>
        /// <param name="applyInfo"></param>
        /// <returns></returns>
        private TradeRefundApplyResult GetApplyToResult(TradeRefundApplyInfo applyInfo)
        {
            var apply = this.repository.FirstOrDefault(b => b.AppId == applyInfo.AppId && b.ApplyNo == applyInfo.ApplyNo);
            if (apply == null)
            {
                return null;
            }
            if (apply.ApplyMoney != applyInfo.ApplyMoney)
            {
                PayExceptionUtils.ThrowPayValidationException("相同的应用和申请号，申请金额必须一致。");
            }
            return apply.MapTo<TradeRefundApplyResult>();
        }

        /// <summary>
        /// 获取退款账本转结果
        /// </summary>
        /// <param name="applyInfo"></param>
        /// <returns></returns>
        private TradeRefundApplyResult GetRefundBookToResult(TradeRefundApplyInfo applyInfo)
        {
            var refundBook = this.refundBookRepositor
                .FirstOrDefault(b => b.AppId == applyInfo.AppId && b.ApplyNo == applyInfo.ApplyNo);
            if (refundBook == null)
            {
                return null;
            }
            if (refundBook.ApplyMoney != applyInfo.ApplyMoney)
            {
                PayExceptionUtils.ThrowPayValidationException("相同的应用和申请号，申请金额必须一致。");
            }
            //  var result = MapperUtils.Map<TradeRefundBook, TradeRefundApplyResult>(refundBook);
            var result = MapResult(refundBook);
            result.ApplyState = TradeApplyState.Execute_Success;
            result.ApplyStateMessage = "退款成功";
            return result;
        }

        /// <summary>
        /// 检查
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="orderNo"></param>
        /// <param name="applyMoney"></param>
        /// <param name="isCheckCurrnetMoney"></param>
        /// <returns></returns>
        private TradeAccountBookChannel Check(string appId, string orderNo, decimal applyMoney,
            bool isCheckCurrnetMoney)
        {
            var book = this.bookRepository.FirstOrDefault(b => b.AppId == appId
                                && b.OrderNo == orderNo);
            if (book == null)
            {
                PayExceptionUtils.ThrowPayValidationException("订单号为:" + orderNo + " 未支付成功，不能进行退款申请");
            }
            if (book.TradeAccountBookChannels == null || book.TradeAccountBookChannels.Count == 0)
            {
                PayExceptionUtils.ThrowPayValidationException("订单号为:" + orderNo + " 无法获取对应的支付账本通道，不能进行退款申请");
            }
            if (book.TradeBalanceMoney < applyMoney)
            {
                PayExceptionUtils.ThrowPayValidationException("订单号为:" + orderNo + " 可退余额不足。");
            }
            if (isCheckCurrnetMoney)
            {
                var sumApplyMoney = this.repository.GetAll()
                                .Where(c => c.AppId == appId && c.OrderNo == orderNo)
                                .Select(s => s.ApplyMoney)
                                .ToList().Sum();
                if (sumApplyMoney + applyMoney > book.TradeBalanceMoney)
                {
                    PayExceptionUtils.ThrowPayValidationException("订单号为:" + orderNo + " 可退余额不足。");
                }
            }
            var channel = book.TradeAccountBookChannels.FirstOrDefault(t => t.TradeBalanceMoney >= applyMoney);
            if (channel == null)
            {
                PayExceptionUtils.ThrowPayValidationException("订单号为:" + orderNo + " 无任何支付通道的可退余额大于申请金额，若订单出现多次支付，请减少申请金进行多次退款申请");
            }
            return channel;
        }

        /// <summary>
        /// 创建申请
        /// </summary>
        /// <param name="bookChannel"></param>
        /// <param name="applyInfo"></param>
        /// <returns></returns>
        private TradeRefundApply CreateApply(TradeAccountBookChannel bookChannel, TradeRefundApplyInfo applyInfo)
        {
            var app = this.tradeCacheService.GetMerchantAppInfo(applyInfo.AppId);
            if (app == null)
            {
                PayExceptionUtils.ThrowPayValidationException("对应的应用不存在或状态不正常");
            }
            var channel = tradeCacheService.GetTradeChannel(app.Id, bookChannel.ChannelId);
            if (channel == null)
            {
                PayExceptionUtils.ThrowPayValidationException("对应的支付通道未配置");
            }
            if (!channel.ChannelConfigure.ConfigureInfo.IsSupportRefund)
            {
                PayExceptionUtils.ThrowPayValidationException("指定的支方式不支持退款");
            }
            return new TradeRefundApply()
            {
                AppId = applyInfo.AppId,
                AppIp = ClientInfo.ClientIpAddress == null ? "" : ClientInfo.ClientIpAddress,
                ApplyMoney = applyInfo.ApplyMoney,
                ApplyNo = applyInfo.ApplyNo,
                ApplyState = TradeApplyState.Apply_Success,
                ApplyStateMessage = "申请成功，等待退款",
                ApplyTime = DateTime.Now,
                ChannelId = bookChannel.ChannelId,
                ExtraParam = applyInfo.ExtraParam,
                NotifyUrl = string.IsNullOrWhiteSpace(applyInfo.NotifyUrl) ? app.RefundDefaultNotifyUrl : applyInfo.NotifyUrl,
                Reason = applyInfo.Reason,
                RefundTradeNo = null,
                TradeNo = bookChannel.Id,
                OrderNo = applyInfo.OrderNo,
                LastSubmitTime = null
            };
        }

        #endregion       

        private TradeRefundApplyResult MapResult(TradeRefundApply apply)
        {
            return new TradeRefundApplyResult()
            {
                AppId = apply.AppId,
                ApplyMoney = apply.ApplyMoney,
                ApplyNo = apply.ApplyNo,
                ApplyState = apply.ApplyState,
                ApplyStateMessage = apply.ApplyStateMessage,
                ChannelId = apply.ChannelId,
                OrderNo = apply.OrderNo,
                Reason = apply.Reason,
                TradeNo = apply.TradeNo
            };
        }

        private TradeRefundApplyResult MapResult(TradeRefundBook apply)
        {
            return new TradeRefundApplyResult()
            {
                AppId = apply.AppId,
                ApplyMoney = apply.ApplyMoney,
                ApplyNo = apply.ApplyNo,
                ApplyState = TradeApplyState.Execute_Success,
                ApplyStateMessage = "退款成功",
                ChannelId = apply.ChannelId,
                OrderNo = apply.OrderNo,
                Reason = apply.Reason,
                TradeNo = apply.TradeNo
            };
        }

        /// <summary>
        /// 退款申请
        /// </summary>
        /// <param name="applyValue">申请</param>
        /// <returns></returns>
        public TradeRefundApplyResult RefundApply(TradeRefundApplyInfo applyInfo)
        {
            applyInfo.Validate();
            //已申请过，返回申请过的结果
            var result = this.GetApplyToResult(applyInfo);
            if (result != null)
            {
                return result;
            }
            //已申请，并且已退款成功，返回结果
            result = this.GetRefundBookToResult(applyInfo);
            if (result != null)
            {
                return result;
            }
            //检查是否存在订单、退款金额是否已超出
            var bookChannel = this.Check(applyInfo.AppId, applyInfo.OrderNo, applyInfo.ApplyMoney, true);
            var apply = this.CreateApply(bookChannel, applyInfo);

            this.repository.Insert(apply);

            //  result = MapperUtils.Map<TradeRefundApply, TradeRefundApplyResult>(apply);

            result = this.MapResult(apply);
            repository.SaveChanges();
            var msg = new RabbitMQ.GNClient.ObjectMessage(apply);
            msg.MessageId = "0";
            queueManager.Publish(QueueType.ClientRefundApply, msg);
            return result;
        }

        private static object refundNo_Lock = new object();

        /// <summary>
        /// 生成退款单号锁
        /// </summary>
        /// <param name="oldRefundNo"></param>
        /// <returns></returns>
        private string GenerateRefundNo(string oldRefundNo)
        {
            lock (refundNo_Lock)
            {
                string newRefundNo = DateTime.Now.ToString("yyyyMMddHHmmssffffff");
                if (oldRefundNo != null && oldRefundNo.Length >= 8 && newRefundNo.Substring(0, 8).Equals(oldRefundNo.Substring(0, 8)))
                {
                    return oldRefundNo;
                }
                return newRefundNo;
            }
        }

        /// <summary>
        /// 退款申请验证
        /// </summary>
        /// <param name="apply"></param>
        /// <returns></returns>
        private TradeAccountBookChannel RefundValidate(ref TradeRefundApply apply, out ITradeChannel channel)
        {
            if (apply == null)
            {
                PayExceptionUtils.ThrowPayValidationException("申请信息不能空 null");
            }
            apply = this.repository.Get(apply.Id);
            if (apply == null)
            {
                PayExceptionUtils.ThrowPayValidationException("指定的申请对应的数据为 null");
            }
            if (apply.ApplyState == TradeApplyState.Executeing)
            {
                PayExceptionUtils.ThrowPayValidationException("指定的申请正在执行，不能再执行");
            }
            channel = tradeCacheService.GetTradeChannel(apply.AppId, apply.ChannelId);
            if (channel == null)
            {
                PayExceptionUtils.ThrowPayValidationException("无法获取通道信息");
            }
            return this.Check(apply.AppId, apply.OrderNo, apply.ApplyMoney, false);
        }

        /// <summary>
        /// 执行退款,此服务不可对外Api
        /// </summary>
        /// <param name="apply">申请</param>
        public TradeApplyResultState Refund(TradeRefundApply apply)
        {
            ITradeChannel channel;
            var bookChannel = this.RefundValidate(ref apply, out channel);
            string refundNo = GenerateRefundNo(apply.RefundTradeNo);
            var info = new TradeChannel.DefaultImpl.RefundApplyInfo()
            {
                ApplyMoney = apply.ApplyMoney,
                ApplyTime = DateTime.Now,
                PayTime = bookChannel.PayTime,
                ExtraParam = apply.ChannelId,
                OrderMoney = bookChannel.PayMoney,
                OrderNo = bookChannel.Id,
                RefundReason = apply.Reason,
                TradeNo = bookChannel.ChannelTradeNo,
                RefundNo = refundNo
            };
            apply.LastSubmitTime = DateTime.Now;
            apply.RefundTradeNo = refundNo;
            ITradeRefundApplyResult result;
            string opName = "退款";
            try
            {
                this.CallApply<TradeRefundApply, long, ITradeRefundApplyRepository>(apply, repository, opName);
                result = channel.RefundApply(info);
                if (result == null)
                {
                    PayExceptionUtils.ThrowPaySystemException("支付系统接口出现异常，退款申请结果为 null");
                }
                this.CallApplyResult<TradeRefundApply, long, ITradeRefundApplyResult, ITradeRefundApplyRepository>(apply,
                    result,
                    this.repository,
                    opName, (s, r) =>
                    {
                        if (r.ResultState == TradeApplyResultState.Complete_Success)
                        {
                            s.RefundNotify(r);
                        }
                        else
                        {
                            var app = tradeCacheService.GetMerchantAppInfo(apply.AppId);
                            if (app != null && !string.IsNullOrWhiteSpace(apply.NotifyUrl))
                            {
                                var message = apply.MapTo<RefundNotifyData>();
                                message.ExtraParam = apply.ExtraParam;
                                message.TradeNo = apply.TradeNo;
                                message.Success = false;
                                message.StateMessage = r.ResultStateMessage;
                                message.ChannelName = channel.ChannelName;
                                var notify = s.PullNotify(app, TradeNotifyType.Refund, apply.NotifyUrl, null, message);
                            }
                        }
                    });
                return result.ResultState;
            }
            catch (Exception err)
            {
                this.Logger.Error("通道 " + channel.ChannelId + " 退款发生异常:" + err.Message + " stackTrace:" + err.StackTrace);
                apply.ApplyState = TradeApplyState.Execute_Error;
                apply.ApplyStateMessage = err.Message + " stackTrace:" + err.StackTrace;
                this.repository.Update(apply);
                return TradeApplyResultState.Apply_Error;
            }
        }
    }
}
